package fileProcessors.implementations;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import math.Statistics;
import asciiTablePrinting.ASCIIBooleanColumn;
import asciiTablePrinting.ASCIIDoubleColumn;
import asciiTablePrinting.ASCIIStringColumn;
import asciiTablePrinting.ASCIITable;
import fileProcessors.FileFormatException;
import fileProcessors.FileProcessor;

/**
 * 
 * File Processor for doing steel ball data analysis. The files have to be
 * specified as shown in DataReader.
 * 
 * @author Wolfgang Gross, University of Bayreuth
 * @see DataReader
 * @version 1.1.0 (05.04.2014)<br>
 *          -added Long model for nonlinearity. The full model with calculation
 *          of alpha and beta is applied.<br>
 *          -added long error analysis<br>
 *          -implement long validity conditions<br>
 *          -Todo: complete documentation<br>
 *          1.0.1 (23.03.2014)<br>
 *          -adjusted indentation print precision in output files to measurement
 *          precision<br>
 *          -added display of sample count in result sheet<br>
 *          1.0.0 (06.03.2014)<br>
 *          -completed documentation<br>
 *          -added checking of dimitriadis conditions<br>
 */
public class StahlballAuswertung extends FileProcessor {

	/*
	 * (non-Javadoc)
	 * 
	 * @see fileProcessors.FileProcessing#processFiles(java.io.File[],
	 * java.io.File, java.io.File)
	 */
	public final void processFiles(File files[], File currentDirectory, File outputFile)
			throws FileFormatException {

		ArrayList<Data> fileData = new ArrayList<Data>();

		for (File file : files) {
			Data currentData = DataReader.readData(file);
			currentData.calculateAll();

			fileData.add(currentData);

			DataPrinting printer = new ExtendedDataPrinter();
			String outputFileName = file.getName().substring(0, file.getName().length() - 4)
					+ "_extended_results.dat";
			printer.printData(currentData, outputFileName, file.getParentFile());
		}
		// sort fileData for printing overview
		Collections.sort(fileData);

		ASCIITable overviewTable = getOverviewTable(fileData);
		String headline = getHeadline();
		try {
			BufferedWriter outputFileWriter = new BufferedWriter(new FileWriter(outputFile));
			outputFileWriter.write(overviewTable.getTable());

			outputFileWriter.newLine();
			outputFileWriter.newLine();

			outputFileWriter.write(calculateMeanOverSamples(fileData));

			outputFileWriter.newLine();
			outputFileWriter.newLine();

			outputFileWriter.write(getDimitriadisConditionErrors(fileData));
			outputFileWriter.newLine();
			outputFileWriter.write(getLongConditionErrors(fileData));

			outputFileWriter.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private String getLongConditionErrors(ArrayList<Data> fileData) {
		String ret = "";
		for (Data d : fileData) {
			ArrayList<Boolean> rdivhvalid = d.getLongRDivHValid();
			ArrayList<Boolean> deltadivhvalid = d.getLongDeltaDivHValid();
			for (int i = 0; i < rdivhvalid.size(); i++) {
				if (!rdivhvalid.get(i)) {
					ret += "Long Warning: Long geometry condition in gel " + d.sampleName + "(r="
							+ d.radius * 1E6 + "um) at position " + d.lineNames.get(i)
							+ " is not valid!\n";
				}
				if (!deltadivhvalid.get(i)) {
					ret += "Long Warning: delta/h in gel " + d.sampleName + "(r=" + d.radius * 1E6
							+ "um) at position " + d.lineNames.get(i) + " is not valid! \n";
				}
			}
		}
		return ret.trim();
	}

	private String getDimitriadisConditionErrors(ArrayList<Data> fileData) {
		String ret = "";
		for (Data d : fileData) {
			ArrayList<Boolean> indentLeqHeightPerc = d.getIndentLessThenLinHeightPercentage();
			ArrayList<Boolean> chiLeqOne = d.getChiLessThanOne();
			for (int i = 0; i < indentLeqHeightPerc.size(); i++) {
				if (!indentLeqHeightPerc.get(i)) {
					ret += "Dim Warning: indentation in gel " + d.sampleName + "(r=" + d.radius
							* 1E6 + "um) at position " + d.lineNames.get(i) + " is larger than "
							+ Data.MAX_INDENT_PERCENTAGE_FOR_LINEARITY + "h!\n";
				}
				if (!chiLeqOne.get(i)) {
					ret += "Dim Warning: chi in gel " + d.sampleName + "(r=" + d.radius * 1E6
							+ "um) at position " + d.lineNames.get(i) + " is "
							+ d.getDimChi().get(i) + " > 1!\n";
				}
			}
		}
		return ret.trim();
	}

	private String calculateMeanOverSamples(ArrayList<Data> fileData) {
		// ###############TODO######################
		// quick and dirty
		// mittelwert f�r dim und e_h
		ArrayList<Double> hertz_mod = new ArrayList<Double>();
		ArrayList<Double> dim_mod = new ArrayList<Double>();
		ArrayList<Double> hertz_err = new ArrayList<Double>();
		ArrayList<Double> dim_err = new ArrayList<Double>();
		ArrayList<Double> long_mod_sl = new ArrayList<Double>();
		ArrayList<Double> long_mod_sl_err = new ArrayList<Double>();
		ArrayList<Double> long_mod_no_sl = new ArrayList<Double>();
		ArrayList<Double> long_mod_no_sl_err = new ArrayList<Double>();
		for (int i = 0; i < fileData.size(); i++) {
			hertz_mod.add(fileData.get(i).getMeanElasticModulusHertz());
			hertz_err.add(fileData.get(i).getMeanElasticModulusHertzError());
			dim_mod.add(fileData.get(i).getMeanElasticModulusDimitriadis());
			dim_err.add(fileData.get(i).getMeanElasticModulusDimitriadisError());
			long_mod_sl.add(fileData.get(i).getMeanElasticModulusLongSlip());
			long_mod_sl_err.add(fileData.get(i).getMeanElasticModulusLongSlipError());
			long_mod_no_sl.add(fileData.get(i).getMeanElasticModulusLongNoSlip());
			long_mod_no_sl_err.add(fileData.get(i).getMeanElasticModulusLongNoSlipError());
		}
		String ret = "";
		ret += "<E_hertz>/kPa\t\t" + Statistics.mean(hertz_mod) / 1.E3 + "\n";
		ret += "s_<E_hertz>/kPa\t\t" + Data.calculateMeanValueError(hertz_mod, hertz_err) / 1.E3
				+ "\n\n";
		ret += "<E_dim>/kPa\t\t" + Statistics.mean(dim_mod) / 1.E3 + "\n";
		ret += "s_<E_dim>/kPa\t\t" + Data.calculateMeanValueError(dim_mod, dim_err) / 1.E3 + "\n\n";
		ret += "<E_Long_S>/kPa\t\t" + Statistics.mean(long_mod_sl) / 1.E3 + "\n";
		ret += "s_<E_Long_S>/kPa\t" + Data.calculateMeanValueError(long_mod_sl, long_mod_sl_err)
				/ 1.E3 + "\n\n";
		ret += "<E_Long_NS>/kPa\t\t" + Statistics.mean(long_mod_no_sl) / 1.E3 + "\n";
		ret += "s_<E_Long_NS>/kPa\t"
				+ Data.calculateMeanValueError(long_mod_no_sl, long_mod_no_sl_err) / 1.E3 + "\n";
		return ret;
	}

	private String getHeadline() {
		Date timestamp = new Date();
		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
		String headline = "Processed :" + simpleDateFormat.format(timestamp)
				+ " using version from" + getDate() + "\n\n";
		return headline;
	}

	private ASCIITable getOverviewTable(ArrayList<Data> fileData) {
		ASCIITable overviewTable = new ASCIITable();

		// gather Data
		ArrayList<String> descriptions = new ArrayList<String>();
		ArrayList<String> count = new ArrayList<String>();
		ArrayList<Double> minHeight = new ArrayList<Double>();
		ArrayList<Double> maxHeight = new ArrayList<Double>();
		ArrayList<Double> radii = new ArrayList<Double>();
		ArrayList<Double> poisson_ratios = new ArrayList<Double>();
		ArrayList<Double> mean_e_hertz = new ArrayList<Double>();
		ArrayList<Double> mean_e_hertz_error = new ArrayList<Double>();
		ArrayList<Double> mean_e_dim = new ArrayList<Double>();
		ArrayList<Double> mean_e_dim_error = new ArrayList<Double>();
		ArrayList<Double> mean_e_long_slip = new ArrayList<Double>();
		ArrayList<Double> mean_e_long_slip_error = new ArrayList<Double>();
		ArrayList<Double> mean_e_long_no_slip = new ArrayList<Double>();
		ArrayList<Double> mean_e_long_no_slip_error = new ArrayList<Double>();
		for (Data d : fileData) {
			descriptions.add(d.sampleName);
			count.add("" + d.indentations.size());
			minHeight.add(d.getMinHeight());
			maxHeight.add(d.getMaxHeight());
			radii.add(d.radius);
			poisson_ratios.add(d.poissonValue);
			mean_e_hertz.add(d.getMeanElasticModulusHertz());
			mean_e_hertz_error.add(d.getMeanElasticModulusHertzError());
			mean_e_dim.add(d.getMeanElasticModulusDimitriadis());
			mean_e_dim_error.add(d.getMeanElasticModulusDimitriadisError());
			mean_e_long_slip.add(d.getMeanElasticModulusLongSlip());
			mean_e_long_slip_error.add(d.getMeanElasticModulusLongSlipError());
			mean_e_long_no_slip.add(d.getMeanElasticModulusLongNoSlip());
			mean_e_long_no_slip_error.add(d.getMeanElasticModulusLongNoSlipError());
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("sample", "-"));
			overviewTable.addColumn(new ASCIIStringColumn(headlines, descriptions, 7));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("count", "-"));
			overviewTable.addColumn(new ASCIIStringColumn(headlines, count, 7));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("poisson", "-"));
			overviewTable.addColumn(new ASCIIDoubleColumn(headlines, poisson_ratios, 0, 2, 10));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("radius", "um"));
			overviewTable.addColumn(new ASCIIDoubleColumn(headlines, radii, -6, 0, 7));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("<E_Hertz>", "kPa"));
			overviewTable.addColumn(new ASCIIDoubleColumn(headlines, mean_e_hertz, 3, 3, 10));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("s_<E_Hertz>", "kPa"));
			overviewTable.addColumn(new ASCIIDoubleColumn(headlines, mean_e_hertz_error, 3, 3, 12));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("<E_Dim>", "kPa"));
			overviewTable.addColumn(new ASCIIDoubleColumn(headlines, mean_e_dim, 3, 3, 10));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("s_<E_Dim>", "kPa"));
			overviewTable.addColumn(new ASCIIDoubleColumn(headlines, mean_e_dim_error, 3, 3, 10));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("<E_Long_S>", "kPa"));
			overviewTable.addColumn(new ASCIIDoubleColumn(headlines, mean_e_long_slip, 3, 3, 12));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("<s_<E_Long_S>", "kPa"));
			overviewTable.addColumn(new ASCIIDoubleColumn(headlines, mean_e_long_slip_error, 3, 3,
					15));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("<E_Long_NS>", "kPa"));
			overviewTable
			.addColumn(new ASCIIDoubleColumn(headlines, mean_e_long_no_slip, 3, 3, 14));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("s_<E_Long_NS>", "kPa"));
			overviewTable.addColumn(new ASCIIDoubleColumn(headlines, mean_e_long_no_slip_error, 3,
					3, 15));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("minHeight", "um"));
			overviewTable.addColumn(new ASCIIDoubleColumn(headlines, minHeight, -6, 1, 10));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("maxHeight", "um"));
			overviewTable.addColumn(new ASCIIDoubleColumn(headlines, maxHeight, -6, 1, 10));
		}
		return overviewTable;
	}

	@Override
	public String getInformation() {
		String ret = "t Auswertung Stahlballmethode\n";
		ret += "Berechnet aus einem Eingabefile das Elastizitätsmodul nach Hertz, Dimitriadis und Long. "
				+ "Dazu wird die Eindringtiefe einer Stahlkugle mit Radius r in ein Gel mit "
				+ "endlicher H�he h verwendet\nFile Processor for doing steel ball data analysis. The files have to be specified as shown in DataReader.author Wolfgang GroßUniversity of Bayreuth @version 1.1 (05.04.2014)";
		return ret;
	}

	@Override
	public String getDate() {
		return "Bayreuth, 05.04.2014";
	}

	@Override
	public String getTxtProcessBtn() {
		return "E Modul";
	}

	@Override
	public String getTxtIncludeFilename() {
		return ".dat";
	}

	@Override
	public String getTxtExcludeFilename() {
		return "_results.dat";
	}

}

/**
 * Class for reading files in the format:<br>
 * 46_1 <br>
 * 20 //Temperature<br>
 * 200 //sphere radius /um<br>
 * 3. //sphere radius error /um<br>
 * 0.5 //poisson ratio<br>
 * 0.05 //error poisson ratio<br>
 * 7765 //sphere density /kg/m^3<br>
 * 50 //sphere density error kg/m^3<br>
 * 1000 //water density /kg/m^2<br>
 * 30 //water density error/kg/m^3<br>
 * 2. //sphere cap height in water/radius<br>
 * 0 //error sphere cap height in water/radius<br>
 * 1. //error indentation /um (see below)<br>
 * 3 //gel height error /um<br>
 * <br>
 * descr ind height<br>
 * x1y1 3.0 46<br>
 * x2y1 3.8 42<br>
 * x3y1 2.4 33<br>
 * x3y2 3.4 38<br>
 * x2y2 3.8 47<br>
 * x1y2 3.6 50<br>
 * x1y3 3.0 47<br>
 * x2y3 3.2 45<br>
 * x3y3 2.4 39 <br>
 * 
 * @author Wolfgang Groß, University of Bayreuth
 * @see DataReader
 * @version 1.0 (06.03.2014)<br>
 *          completed documentation
 */
class DataReader {
	/**
	 * Reads steel ball data from a file
	 * 
	 * @param file
	 *            The file, which contains the data in the specified format.
	 * @return Data An instance containing the read dataset.
	 */
	public static Data readData(File file) {
		try {
			BufferedReader br = new BufferedReader(new FileReader(file));
			Data data = new Data();
			/*
			 * +++++++++++++++ Parameter einlesen +++++++++++++++
			 */
			// sample Name
			data.sampleName = br.readLine().trim();

			// temperature
			String temperatureString = br.readLine().trim();
			data.temperature = getNumberInFirstPosition(temperatureString, file);

			// radius
			String radiusString = br.readLine().trim();
			data.radius = getNumberInFirstPosition(radiusString, file) * 1E-6;

			// radius error
			String radiusErrorString = br.readLine().trim();
			data.radiusError = getNumberInFirstPosition(radiusErrorString, file) * 1E-6;

			// poisson value
			String poissonValueString = br.readLine().trim();
			data.poissonValue = getNumberInFirstPosition(poissonValueString, file);

			// poisson value error
			String poissonValueErrorString = br.readLine().trim();
			data.poissonValueError = getNumberInFirstPosition(poissonValueErrorString, file);

			// sphere density
			String sphereDensityString = br.readLine().trim();
			data.sphereDensity = getNumberInFirstPosition(sphereDensityString, file);

			// sphere density error
			String sphereDensityErrorString = br.readLine().trim();
			data.sphereDensityError = getNumberInFirstPosition(sphereDensityErrorString, file);

			// water density
			String waterDensityString = br.readLine().trim();
			data.waterDensity = getNumberInFirstPosition(waterDensityString, file);

			// water density error
			String waterDensityErrorString = br.readLine().trim();
			data.waterDensityError = getNumberInFirstPosition(waterDensityErrorString, file);

			// cap height in water in units of sphere radius
			String capHeightInWaterString = br.readLine().trim();
			data.capHeightInWater = getNumberInFirstPosition(capHeightInWaterString, file)
					* data.radius;

			// cap height in water error in units of sphere radius
			String capHeightInWaterErrorString = br.readLine().trim();
			data.capHeightInWaterError = getNumberInFirstPosition(capHeightInWaterErrorString, file)
					* data.radius;

			// indentation Error
			String indentationErrorString = br.readLine().trim();
			data.indentationError = getNumberInFirstPosition(indentationErrorString, file) * 1E-6;

			// gel height Error
			String gelHeightErrorString = br.readLine().trim();
			data.gelHeightError = getNumberInFirstPosition(gelHeightErrorString, file) * 1E-6;

			// skip empty line
			br.readLine();
			// skip header line
			br.readLine();

			// read Table, tab or whitespace delimited
			data.lineNames = new ArrayList<String>();
			data.indentations = new ArrayList<Double>();
			data.gelHeights = new ArrayList<Double>();

			String nextLine = br.readLine();
			while (nextLine != null) {
				nextLine.trim();
				String[] lineSplit = nextLine.split("\\s+");
				if (lineSplit.length != 3) {
					br.close();
					throw new FileFormatException("no 3 columns found in line: " + nextLine);
				}
				try {
					data.lineNames.add(lineSplit[0]);
					data.indentations.add(Double.parseDouble(lineSplit[1]) * 1E-6);
					data.gelHeights.add(Double.parseDouble(lineSplit[2]) * 1E-6);
				} catch (RuntimeException e) {
					br.close();
					throw new FileFormatException("line " + nextLine
							+ " could not be parsed, syntax Error?");
				}
				nextLine = br.readLine();
			}

			br.close();
			return data;
		} catch (IOException e) {
			throw new FileFormatException();
		}
	}

	/**
	 * Parses the first part of the string to a double value. The number has to
	 * be separated by a whitespace.
	 * 
	 * @param s
	 *            the string to search for
	 * @param file
	 *            the file containing the string
	 * @return the double
	 * @throws FileFormatException
	 *             if the first part of the string is parseable to a double
	 */
	private static double getNumberInFirstPosition(String s, File file) {
		String[] split = s.split("\\s+");
		try {
			double number = Double.parseDouble(split[0]);
			return number;
		} catch (RuntimeException e) {
			throw new FileFormatException("No number found in string" + s + "(File "
					+ file.getName() + ")");
		}
	}

	/**
	 * Prevents instanciation, only static access
	 */
	private DataReader() {
	}
}

/**
 * Worker class which implements the steel ball method. All units are set in SI.
 * The standard Hertz model, the Dimitriadis model (Dimitriadis et al., 2002) as
 * well as a model from Long et al. (Long 2012) are supported.<br>
 * 
 * Use it by setting the input parameters and call calculateAll()
 * 
 * 
 * 
 * @author Wolfgang Gross, University of Bayreuth
 * @version 1.0 (06.03.2014)<br>
 *          completed documentation
 * @see Data#calculateAll()
 */
class Data implements Comparable<Data> {
	/*
	 * All parameters and values are in SI units
	 */
	/**
	 * Graviational acceleration in m^2/s
	 */
	public static final double GRAVITATIONAL_ACCELERATION = 9.81;
	/**
	 * Maximum indentation/height when the gel is still considered to be linear.
	 * Note that the dimitriadis model is only linear. Only for checking...
	 */
	public static final double MAX_INDENT_PERCENTAGE_FOR_LINEARITY = 0.1;
	/*
	 * Dimitriadis Model is calculated for a substrate which is bond to the
	 * activated coverslip
	 */
	private static final double DIMITRIADIS_PARAM_ALPHA0_0 = 1.2876;
	private static final double DIMITRIADIS_PARAM_ALPHA0_1 = -1.4678;
	private static final double DIMITRIADIS_PARAM_ALPHA0_2 = 1.3442;

	private static final double DIMITRIADIS_PARAM_BETA0_0 = 0.6387;
	private static final double DIMITRIADIS_PARAM_BETA0_1 = -1.0277;
	private static final double DIMITRIADIS_PARAM_BETA0_2 = 1.5164;

	/*
	 * Long model parameters
	 */
	// parameters for psi
	private static final double LONG_PARAM_PSI_0 = 1;
	private static final double LONG_PARAM_PSI_1 = 2.3;
	private static final double LONG_PARAM_PSI_2 = 1;
	private static final double LONG_PARAM_PSI_3 = 1.15;
	// params for alpha
	private static final double LONG_PARAM_SLIP_ALPHA_0 = 10.05;
	private static final double LONG_PARAM_SLIP_ALPHA_1 = -0.63;
	private static final double LONG_PARAM_SLIP_ALPHA_2 = 3.1;
	private static final double LONG_PARAM_NO_SLIP_ALPHA_0 = 8.94;
	private static final double LONG_PARAM_NO_SLIP_ALPHA_1 = -0.89;
	// params for beta
	private static final double LONG_PARAM_SLIP_BETA_0 = 4.8;
	private static final double LONG_PARAM_SLIP_BETA_1 = -4.23;
	private static final double LONG_PARAM_NO_SLIP_BETA_0 = 9.288;

	// validity bounds
	public static final double LONG_R_DIV_H_VALIDITY_LOWER_BOUND = 0.5;
	public static final double LONG_R_DIV_H_VALIDITY_UPPER_BOUND = 12.7;
	public static final double LONG_DELTA_DIV_H_VALIDITY_UPPER_BOUND = 0.6;

	// numerical error for psi
	/**
	 * Numerical relative fitting error (see paper)
	 */
	private static final double LONG_PARAM_PSI_RELATIVE_ERROR = 0.08;

	// ######################################
	// INPUT PARAMETERS
	// ######################################

	/**
	 * name of the sample
	 */
	public String sampleName;
	/**
	 * temperature of the sample in C
	 */
	public double temperature;
	/**
	 * radius of the steel sphere used in m
	 */
	public double radius;
	/**
	 * error of the radius of the steel sphere used in m
	 */
	public double radiusError;
	/**
	 * poisson value of the gel
	 */
	public double poissonValue;
	/**
	 * error of poisson value of the gel
	 */
	public double poissonValueError;
	/**
	 * density of the steel sphere in kg/m^3
	 */
	public double sphereDensity;
	/**
	 * error of the density of the steel sphere in kg/m^3
	 */
	public double sphereDensityError;
	/**
	 * density of the medium surrounding the sphere in kg/m^3
	 */
	public double waterDensity;
	/**
	 * error of the medium surrounding the sphere in kg/m^3
	 */
	public double waterDensityError;
	/**
	 * height of the cap of the sphere which is in the surrounding medium in m
	 */
	public double capHeightInWater;
	/**
	 * error of the cap height in water in m
	 */
	public double capHeightInWaterError;
	/**
	 * error of the indentation into the gel in m
	 */
	public double indentationError;
	/**
	 * error of the gel height in m
	 */
	public double gelHeightError;
	/**
	 * 1st column: description of the line/coordinates where the sphere was
	 */
	public ArrayList<String> lineNames;
	/**
	 * indentation depths of the spheres in the gel in m
	 */
	public ArrayList<Double> indentations;
	/**
	 * 3rd column: height of the gel at the location in m
	 */
	public ArrayList<Double> gelHeights;

	// ###########################
	// General information
	// ###########################
	/**
	 * volume of the steel sphere in m^3
	 */
	private double sphereVolume; //

	/**
	 * volume of the cap of the sphere which is in water in m^3
	 */
	private double sphereCapInWaterVolume;
	/**
	 * buoyancy force of the sphere in the surrounding medium (only for the cap
	 * which is below water level) in N
	 */
	private double buoyancyForce;
	/**
	 * gravitational force of the sphere in N
	 */
	private double gravitationalForce;
	/**
	 * sum of gravitational and buoyancy force in N
	 */
	private double indentationForce;
	/**
	 * error of the indentation force in N
	 */
	private double indentationForceError;
	/**
	 * minimum gel height in m
	 */
	private double minHeight;
	/**
	 * maximum gel height in m
	 */
	private double maxHeight;

	// ######################################
	// Dimitriadis calculation fields
	// ######################################
	/**
	 * alpha0 parameter of the bonded dimitriadis model
	 */
	private double dimAlpha0Bonded;
	/**
	 * Error of the alpha0 parameter of the bonded dimitriadis model
	 */
	private double dimAlpha0BondedError;
	/**
	 * beta0 parameter of the bonded dimitriadis model
	 */
	private double dimBeta0Bonded;
	/**
	 * error of the beta 0 parameter of the bonded dimitriadis model
	 */
	private double dimBeta0BondedError;
	/**
	 * chi parameter of the dimitriadis model
	 */
	private ArrayList<Double> dimChi;
	/**
	 * error of the chi parameter of the dimitriadis model
	 */
	private ArrayList<Double> dimChiError;

	// #####################################
	// Long calculation fields
	// #####################################
	/**
	 * omega parameter of the long model
	 */
	private ArrayList<Double> longOmega;

	// slipping condition
	/**
	 * psi parameter of the slipping Long Model
	 */
	private ArrayList<Double> longSlipPsi;
	/**
	 * error of psi parameter of the slipping Long Model
	 */
	private ArrayList<Double> longSlipPsiError;
	/**
	 * alpha parameter of the slipping Long model
	 */
	private ArrayList<Double> longSlipAlpha;
	/**
	 * beta parameter of the slipping long model
	 */
	private ArrayList<Double> longSlipBeta;

	// no slipping
	/**
	 * psi parameter of the non-slipping Long Model
	 */
	private ArrayList<Double> longNoSlipPsi;
	/**
	 * error of psi parameter of the non-slipping Long Model
	 */
	private ArrayList<Double> longNoSlipPsiError;
	/**
	 * alpha parameter of the non-slipping Long model
	 */
	private ArrayList<Double> longNoSlipAlpha;
	/**
	 * beta parameter of the non-slipping long model
	 */
	private ArrayList<Double> longNoSlipBeta;

	// #################################
	// Elastic properties
	// #################################
	/**
	 * Mean Hertz elastic modulus of all data points in the dataset in Pa
	 */
	private double meanElasticModulusHertz;
	/**
	 * error of the ean Hertz elastic modulus of all data points in the dataset
	 * in Pa
	 */
	private double meanElasticModulusHertzError;
	/**
	 * Mean Dimitriadis elastic modulus of all data points in the dataset in Pa
	 */
	private double meanElasticModulusDimitriadis;
	/**
	 * error of the Mean Dimitriadis elastic modulus of all data points in the
	 * dataset in Pa
	 */
	private double meanElasticModulusDimitriadisError;
	/**
	 * Mean Long elastic modulus of all data points in the dataset in Pa for no
	 * slip condition
	 */
	private double meanElasticModulusLongNoSlip;
	/**
	 * error of the Mean Long elastic modulus of all data points in the dataset
	 * for no slip condition in Pa
	 */
	private double meanElasticModulusLongNoSlipError;
	/**
	 * Mean Long elastic modulus of all data points in the dataset in Pa for
	 * slip condition
	 */
	private double meanElasticModulusLongSlip;
	/**
	 * error of the Mean Long elastic modulus of all data points in the dataset
	 * for slip condition in Pa
	 */
	private double meanElasticModulusLongSlipError;
	private double meanShearModulus;
	private double meanShearModulusError;
	private double meanBulkModulus;
	private double meanBulkModulusError;
	/**
	 * Hertz elastic modulus for all individual datapoints in Pa
	 */
	private ArrayList<Double> elasticModulusHertz;
	/**
	 * Error of the hertz elastic modulus for all individual datapoints in Pa
	 */
	private ArrayList<Double> elasticModulusHertzError;
	/**
	 * Factors of the dimitriadis correction for all individual datapoints
	 */
	private ArrayList<Double> dimitriadisFactors;
	/**
	 * errors of the actors of the dimitriadis correction for all individual
	 * datapoints
	 */
	private ArrayList<Double> dimitriadisFactorErrors;
	/**
	 * Dimitriadis elastic modulus for all individual datapoints in Pa
	 */
	private ArrayList<Double> elasticModulusDimitriadis;
	/**
	 * Errors of the Dimitriadis elastic modulus for all individual datapoints
	 * in Pa
	 */
	private ArrayList<Double> elasticModulusDimitriadisError;
	/**
	 * Dimitriadis elastic modulus for all individual datapoints in Pa
	 */
	private ArrayList<Double> elasticModulusLongSlip;
	/**
	 * Errors of the Dimitriadis elastic modulus for all individual datapoints
	 * in Pa
	 */
	private ArrayList<Double> elasticModulusLongSlipError;
	/**
	 * Dimitriadis elastic modulus for all individual datapoints in Pa
	 */
	private ArrayList<Double> elasticModulusLongNoSlip;
	/**
	 * Errors of the Dimitriadis elastic modulus for all individual datapoints
	 * in Pa
	 */
	private ArrayList<Double> elasticModulusLongNoSlipError;

	private ArrayList<Double> shearModulus; // TODO
	private ArrayList<Double> shearModulusError; // TODO
	private ArrayList<Double> bulkModulus; // TODO
	private ArrayList<Double> bulkModulusError; // TODO

	// Additional information
	/**
	 * Indentation percentage for all individual datapoints
	 */
	private ArrayList<Double> indentationPercent;
	/**
	 * Volume of the indented sperical cap for all individual datapoints in m^3
	 */
	private ArrayList<Double> indentationVolume;
	/**
	 * Surface area of the spherical cup where the spere is touching the gel for
	 * all individual datapoints in m^2
	 */
	private ArrayList<Double> contactArea;
	/**
	 * Force per contact area in N/m^2. The Pressure below the sphere is
	 * estimated to be homogenious
	 */
	private ArrayList<Double> forcePerContaceArea;
	/**
	 * whether or not chi is less than one and the dimitriadis correction is
	 * converging for all individual datapoints.
	 */
	private ArrayList<Boolean> dimChiLessThanOne;
	/**
	 * wheter or not the indentation is less than the
	 * MAX_INDENT_PERCENTAGE_FOR_LINEARITY*gel height for all individual
	 * datapoints.
	 * 
	 * @see Data#MAX_INDENT_PERCENTAGE_FOR_LINEARITY
	 */
	private ArrayList<Boolean> indentLessThenLinHeightPercentage;

	/**
	 * wheter or not the long indentation condition is valid for all individual
	 * datapoints
	 * 
	 * @see Data#LONG_DELTA_DIV_H_VALIDITY_UPPER_BOUND
	 */
	private ArrayList<Boolean> longDeltaDivHValid;

	/**
	 * wheter or not the long geometry condition is valid for all individual
	 * datapoints
	 * 
	 * @see Data#LONG_R_DIV_H_VALIDITY_LOWER_BOUND
	 * @see Data#LONG_R_DIV_H_VALIDITY_UPPER_BOUND
	 */
	private ArrayList<Boolean> longRDivHValid;

	/**
	 * When all the input parameters are set, the model can be executed for the
	 * data. <br>
	 * Input parameters are all the read data by the DataReader.<br>
	 * <br>
	 * The supported models are the standard Hertz model and the dimitriadis
	 * correction (Dimitriadis et. al., 2002) as well as the Long model for
	 * nonlinearity of the material (Long et. al., 2011)
	 * 
	 * @see DataReader
	 * @see DataReader#readData(File)
	 * 
	 */
	public void calculateAll() {
		sphereVolume = sphereVolume(radius);
		sphereCapInWaterVolume = sphericalCapVolume(radius, capHeightInWater);
		gravitationalForce = gravitationalForce(sphereVolume, sphereDensity);
		buoyancyForce = -gravitationalForce(sphereCapInWaterVolume, waterDensity);
		indentationForce = gravitationalForce + buoyancyForce;
		minHeight = Statistics.min(gelHeights);
		maxHeight = Statistics.max(gelHeights);

		// Elastic properties
		evaluateElasticModuliHertz();
		evaluateElasticModulusDimitriadis();
		evaluateElasticModulusLongSlip();
		evaluateElasticModulusLongNoSlip();

		// Additional information
		evaluateIndentationPercent();
		evaluateIndentationVolume();
		evaluateContactArea();
		evaluateForcePerContactArea();
		checkDimitriadisConditions();
		checkLongConditions();

		// error analysis
		evaluateErrorIndentationForce();
		evaluateErrorsElasticModulusHertz();
		runDimitriadisErrorAnalysis();
		runLongErrorAnalysis();

		// Mean values
		evaluateMeanValues();
	}

	/**
	 * Checks the validity of the Dimitriadis model by checking the value of chi
	 * and the indendation depth
	 */
	private void checkDimitriadisConditions() {
		checkDimitriadisChiLessThanOne();
		checkDimitriadisLinearityCondition();
	}

	/**
	 * Checks the validity of the Dimitriadis model by comparing the gel height
	 * to the indentation depth for all given datapoints.
	 */
	private void checkDimitriadisLinearityCondition() {
		indentLessThenLinHeightPercentage = new ArrayList<Boolean>();
		for (int i = 0; i < indentations.size(); i++) {
			indentLessThenLinHeightPercentage
					.add(indentations.get(i) < MAX_INDENT_PERCENTAGE_FOR_LINEARITY
							* gelHeights.get(i));
		}
	}

	/**
	 * Checks if chi (math.sqrt(delta*R)/h) is smaller than 1 (Convergence
	 * criterium)
	 */
	private void checkDimitriadisChiLessThanOne() {
		dimChiLessThanOne = new ArrayList<Boolean>();
		for (int i = 0; i < indentations.size(); i++) {
			dimChiLessThanOne.add(Math.sqrt(radius * indentations.get(i)) < gelHeights.get(i));
		}
	}

	/**
	 * Checks the validity of the Long model
	 */
	private void checkLongConditions() {
		checkLongRDivHValidity();
		checkLongDeltaDivHValidity();
	}

	private void checkLongDeltaDivHValidity() {
		longDeltaDivHValid = new ArrayList<Boolean>();
		for (int i = 0; i < gelHeights.size(); i++) {
			double ddivh = indentations.get(i) / gelHeights.get(i);
			double rdivh = radius / gelHeights.get(i);
			double upperBound = LONG_DELTA_DIV_H_VALIDITY_UPPER_BOUND;
			if (rdivh < upperBound) {
				upperBound = rdivh;
			}
			longDeltaDivHValid.add(ddivh <= upperBound);
		}
	}

	private void checkLongRDivHValidity() {
		longRDivHValid = new ArrayList<Boolean>();
		for (int i = 0; i < gelHeights.size(); i++) {
			double rdivh = radius / gelHeights.get(i);
			boolean valid = rdivh >= LONG_R_DIV_H_VALIDITY_LOWER_BOUND
					&& rdivh <= LONG_R_DIV_H_VALIDITY_UPPER_BOUND;
			longRDivHValid.add(valid);
		}
	}

	/**
	 * Evaluates the Hertz elastic modulus for all given datapoints.
	 */
	private void evaluateElasticModuliHertz() {
		elasticModulusHertz = new ArrayList<Double>();
		for (double indentation : indentations) {
			elasticModulusHertz.add((3 * (1 - poissonValue * poissonValue) * indentationForce)
					/ (4 * Math.sqrt(radius * indentation * indentation * indentation)));
		}
	}

	/**
	 * Evaluates the Dimitriadis correction factors for all given datapoints
	 * assuming the substrate is bond to the lower coverslip.
	 */
	private void evaluateDimitriadisFactorsSubstrateBondToCS() {
		dimitriadisFactors = new ArrayList<Double>();
		dimChi = new ArrayList<Double>();
		// use formula for gel bond to the coverslip
		dimAlpha0Bonded = -(DIMITRIADIS_PARAM_ALPHA0_0 + DIMITRIADIS_PARAM_ALPHA0_1
				* poissonValue + DIMITRIADIS_PARAM_ALPHA0_2 * poissonValue * poissonValue)
				/ (1 - poissonValue);
		dimBeta0Bonded = (DIMITRIADIS_PARAM_BETA0_0 + DIMITRIADIS_PARAM_BETA0_1 * poissonValue + DIMITRIADIS_PARAM_BETA0_2
				* poissonValue * poissonValue)
				/ (1 - poissonValue);

		for (int i = 0; i < indentations.size(); i++) {
			double indent = indentations.get(i);
			double height = gelHeights.get(i);
			dimChi.add(Math.sqrt(radius * indent) / height);
			double chi_divPi = dimChi.get(i) / Math.PI;

			double order0 = 1;
			double order1 = -2 * dimAlpha0Bonded * chi_divPi;
			double order2 = order1 * order1;
			double order3 = (order1 * order1 * order1)
					- (32 * Math.PI * Math.PI * dimBeta0Bonded * chi_divPi * chi_divPi
							* chi_divPi / 15);
			double order4 = (16 * dimAlpha0Bonded * chi_divPi * chi_divPi * chi_divPi * chi_divPi)
					* (dimAlpha0Bonded * dimAlpha0Bonded * dimAlpha0Bonded + (3 * Math.PI
							* Math.PI * dimBeta0Bonded / 5));

			dimitriadisFactors.add(order0 + order1 + order2 + order3 + order4);
		}

	}

	/**
	 * Evaluates the Dimitriadis elastic modulus for all given datapoints.
	 */
	private void evaluateElasticModulusDimitriadis() {
		evaluateDimitriadisFactorsSubstrateBondToCS();
		elasticModulusDimitriadis = new ArrayList<Double>();
		for (int i = 0; i < elasticModulusHertz.size(); i++) {
			elasticModulusDimitriadis.add(elasticModulusHertz.get(i)
					/ dimitriadisFactors.get(i));
		}
	}

	/**
	 * Evaluates the omega factors for the long model
	 */
	private void evaluateLongOmegas() {
		longOmega = new ArrayList<Double>();
		for (int i = 0; i < indentations.size(); i++) {
			double temp = radius * indentations.get(i)
					/ (gelHeights.get(i) * gelHeights.get(i));
			longOmega.add(Math.pow(temp, 1.5));
		}
	}

	/**
	 * Evaluates the long alpha and beta factors for all given datapoints
	 * assuming the substrate is bond to the lower coverslip? and the sphere gel
	 * interface is slippery
	 */
	private void evaluateLongFactorsSlip() {
		longSlipAlpha = new ArrayList<Double>();
		longSlipBeta = new ArrayList<Double>();
		for (int i = 0; i < indentations.size(); i++) {
			double h_div_R = indentations.get(i) / radius;
			double newAlpha = LONG_PARAM_SLIP_ALPHA_0
					+ (LONG_PARAM_SLIP_ALPHA_1 * Math.sqrt(h_div_R) * (LONG_PARAM_SLIP_ALPHA_2 + h_div_R
							* h_div_R));
			longSlipAlpha.add(newAlpha);
			double newBeta = LONG_PARAM_SLIP_BETA_0 + LONG_PARAM_SLIP_BETA_1 * h_div_R
					* h_div_R;
			longSlipBeta.add(newBeta);
		}
	}

	private void evaluateLongPsiSlip() {
		longSlipPsi = new ArrayList<Double>();
		for (int i = 0; i < indentations.size(); i++) {
			double numerator = LONG_PARAM_PSI_0 + LONG_PARAM_PSI_1 * longOmega.get(i);
			double denominator = LONG_PARAM_PSI_2 + LONG_PARAM_PSI_3
					* Math.pow(longOmega.get(i), 1. / 3.);
			denominator += longSlipAlpha.get(i) * longOmega.get(i);
			denominator += longSlipBeta.get(i) * longOmega.get(i) * longOmega.get(i);
			longSlipPsi.add(numerator / denominator);
		}
	}

	private void evaluateElasticModulusLongSlip() {
		evaluateLongFactorsSlip();
		evaluateLongOmegas();
		evaluateLongPsiSlip();
		elasticModulusLongSlip = new ArrayList<Double>();
		for (int i = 0; i < elasticModulusHertz.size(); i++) {
			elasticModulusLongSlip.add(elasticModulusHertz.get(i) * longSlipPsi.get(i));
		}
	}

	private void evaluateLongFactorsNoSlip() {
		longNoSlipAlpha = new ArrayList<Double>();
		longNoSlipBeta = new ArrayList<Double>();
		for (int i = 0; i < indentations.size(); i++) {
			double h_div_r = indentations.get(i) / this.radius;
			double newAlpha = LONG_PARAM_NO_SLIP_ALPHA_0 + LONG_PARAM_NO_SLIP_ALPHA_1
					* h_div_r * h_div_r;
			longNoSlipAlpha.add(newAlpha);
			longNoSlipBeta.add(LONG_PARAM_NO_SLIP_BETA_0);
		}
	}

	private void evaluateLongPsiNoSlip() {
		longNoSlipPsi = new ArrayList<Double>();
		for (int i = 0; i < indentations.size(); i++) {
			double numerator = LONG_PARAM_PSI_0 + LONG_PARAM_PSI_1 * longOmega.get(i);
			double denominator = LONG_PARAM_PSI_2 + LONG_PARAM_PSI_3
					* Math.pow(longOmega.get(i), 1. / 3.);
			denominator += longNoSlipAlpha.get(i) * longOmega.get(i);
			denominator += longNoSlipBeta.get(i) * longOmega.get(i) * longOmega.get(i);
			longNoSlipPsi.add(numerator / denominator);
		}
	}

	private void evaluateElasticModulusLongNoSlip() {
		evaluateLongFactorsNoSlip();
		evaluateLongOmegas();
		evaluateLongPsiNoSlip();
		elasticModulusLongNoSlip = new ArrayList<Double>();
		for (int i = 0; i < elasticModulusHertz.size(); i++) {
			elasticModulusLongNoSlip.add(elasticModulusHertz.get(i) * longNoSlipPsi.get(i));
		}
	}

	/**
	 * Evaluates the mean values of the elastic modulus (with error) for both
	 * Hertz and Dimitriadis model of all given datapoints.
	 */
	private void evaluateMeanValues() {
		meanElasticModulusHertz = Statistics.mean(elasticModulusHertz);
		meanElasticModulusHertzError = calculateMeanValueError(elasticModulusHertz,
				elasticModulusHertzError);

		meanElasticModulusDimitriadis = Statistics.mean(elasticModulusDimitriadis);
		meanElasticModulusDimitriadisError = calculateMeanValueError(
				elasticModulusDimitriadis, elasticModulusDimitriadisError);

		meanElasticModulusLongNoSlip = Statistics.mean(elasticModulusLongNoSlip);
		meanElasticModulusLongNoSlipError = calculateMeanValueError(elasticModulusLongNoSlip,
				elasticModulusLongNoSlipError);

		meanElasticModulusLongSlip = Statistics.mean(elasticModulusLongSlip);
		meanElasticModulusLongSlipError = calculateMeanValueError(elasticModulusLongSlip,
				elasticModulusLongSlipError);
	}

	/**
	 * Evaluates the indentation percentage of the steel spheres for all given
	 * datapoints.
	 */
	private void evaluateIndentationPercent() {
		indentationPercent = new ArrayList<Double>();
		for (int i = 0; i < indentations.size(); i++) {
			indentationPercent.add(indentations.get(i) / gelHeights.get(i));
		}
	}

	/**
	 * Evaluates the indentation volumes for all given datapoints.
	 */
	private void evaluateIndentationVolume() {
		indentationVolume = new ArrayList<Double>();

		for (int i = 0; i < indentations.size(); i++) {
			double indent = indentations.get(i);
			indentationVolume.add(sphericalCapVolume(radius, indent));
		}
	}

	/**
	 * Evaluates the real contact area (spherical cap) for all given datapoints
	 * given the radius of the spere and the indentation depth.
	 */
	private void evaluateContactArea() {
		contactArea = new ArrayList<Double>();
		for (int i = 0; i < indentations.size(); i++) {
			contactArea.add(sphericalCapSurfaceArea(radius, indentations.get(i)));
		}
	}

	/**
	 * Evaluates the indentaion force per contact area in N/m^2 for all given
	 * datapoints. The real contact area is assumed (not according to the
	 * Hertzian model)
	 */
	private void evaluateForcePerContactArea() {
		forcePerContaceArea = new ArrayList<Double>();
		for (int i = 0; i < indentations.size(); i++) {
			forcePerContaceArea.add(indentationForce / contactArea.get(i));
		}
	}

	/**
	 * Evaluates the error of the indentation force as a result of the errors of
	 * sphere density, surrounding medium density, the error of the cap height
	 * in water and the radius of the sphere. The calculation is performed using
	 * standard error propagation.
	 */
	private void evaluateErrorIndentationForce() {
		double dF_by_drho_s = GRAVITATIONAL_ACCELERATION * sphereVolume;
		double dF_by_drho_w = GRAVITATIONAL_ACCELERATION * sphereCapInWaterVolume;
		double dF_by_dh_cap = GRAVITATIONAL_ACCELERATION * waterDensity * Math.PI
				* capHeightInWater * capHeightInWater;
		double dF_by_dR = GRAVITATIONAL_ACCELERATION
				* Math.PI
				* (4 * sphereDensity * radius * radius - waterDensity * capHeightInWater
						* capHeightInWater);

		// sqare derivatives and multiply error
		dF_by_drho_s *= dF_by_drho_s * sphereDensityError * sphereDensityError;
		dF_by_drho_w *= dF_by_drho_w * waterDensityError * waterDensityError;
		dF_by_dh_cap *= dF_by_dh_cap * capHeightInWaterError * capHeightInWaterError;
		dF_by_dR *= dF_by_dR * radiusError * radiusError;

		// error propagation
		indentationForceError = Math.sqrt(dF_by_drho_w + dF_by_drho_s + dF_by_dh_cap
				+ dF_by_dR);
	}

	/**
	 * Evaluates the errors of the Hertz elastic modulus by combining the errors
	 * of indentation force, sphere radius, poisson value and indentation depth
	 * using standard error propagation.
	 */
	private void evaluateErrorsElasticModulusHertz() {
		elasticModulusHertzError = new ArrayList<Double>();
		for (int i = 0; i < indentations.size(); i++) {
			double indent = indentations.get(i);

			double diff_E_by_Fi = (3 * (1 - poissonValue * poissonValue))
					/ (4 * Math.sqrt(radius * indent));
			double diff_E_by_R = -(3 * (1 - poissonValue * poissonValue) * indentationForce)
					/ (Math.sqrt(8 * indent * indent * indent) * Math.sqrt(radius * radius
							* radius));
			double diff_E_by_nu = 8 * poissonValue * indentationForce
					/ (4 * Math.sqrt(radius * indent) * Math.sqrt(indent * indent));
			double diff_E_by_indent = -(9 * (1 - poissonValue * poissonValue) * indentationForce)
					/ (8 * Math.sqrt(radius * indent * indent) * Math.sqrt(indent * indent
							* indent));

			// error propagation
			diff_E_by_Fi *= diff_E_by_Fi * indentationForceError * indentationForceError;
			diff_E_by_R *= diff_E_by_R * radiusError * radiusError;
			diff_E_by_nu *= diff_E_by_nu * poissonValueError * poissonValueError;
			diff_E_by_indent *= diff_E_by_indent * indentationError * indentationError;

			double error = Math.sqrt(diff_E_by_Fi + diff_E_by_indent + diff_E_by_nu
					+ diff_E_by_R);
			elasticModulusHertzError.add(error);
		}
	}

	/**
	 * runs the error analysis for the dimitriadis model using the given errors
	 * in the input file
	 */
	private void runDimitriadisErrorAnalysis() {
		evaluateErrorAlpha0Dimitriadis();
		evaluateErrorBeta0Dimitriadis();
		evaluateErrorChiDimitriadis();
		evaluateErrorDimitriadisFactors();
		evaluateErrorsElasticModulusDimitriadis();
	}

	/**
	 * runs the error analysis for the long model using the given errors in the
	 * input file
	 */
	private void runLongErrorAnalysis() {
		// slipping
		evaluateErrorsLongSlipPsi();
		evaluateErrorsLongSlipElasticModulus();
		// non slipping
		evaluateErrorsLongNoSlipPsi();
		evaluateErrorsLongNoSlipElasticModulus();
	}

	private void evaluateErrorsLongNoSlipElasticModulus() {
		elasticModulusLongNoSlipError = new ArrayList<Double>();
		for (int i = 0; i < indentations.size(); i++) {
			double t1 = elasticModulusHertz.get(i) * longNoSlipPsiError.get(i);
			double t2 = longNoSlipPsi.get(i) * elasticModulusHertzError.get(i);
			double error = Math.sqrt(t1 * t1 + t2 * t2);
			elasticModulusLongNoSlipError.add(error);
		}
	}

	/**
	 * Evaluates the errors for psi of the long model in the slipping case. Adds
	 * an additional systematic error due to fitting discrepancy (see paper)
	 * 
	 * @see Data#LONG_PARAM_PSI_RELATIVE_ERROR
	 */
	private void evaluateErrorsLongSlipPsi() {
		longSlipPsiError = new ArrayList<Double>();
		for (int i = 0; i < indentations.size(); i++) {
			double r = radius;
			double sr = radiusError;
			double h = gelHeights.get(i);
			double sh = gelHeightError;
			double d = indentations.get(i);
			double sd = indentationError;
			double P0 = LONG_PARAM_PSI_0;
			double P1 = LONG_PARAM_PSI_1;
			double P2 = LONG_PARAM_PSI_2;
			double P3 = LONG_PARAM_PSI_3;
			double A0 = LONG_PARAM_SLIP_ALPHA_0;
			double A1 = LONG_PARAM_SLIP_ALPHA_1;
			double A2 = LONG_PARAM_SLIP_ALPHA_2;
			double B0 = LONG_PARAM_SLIP_BETA_0;
			double B1 = LONG_PARAM_SLIP_BETA_1;

			double t1 = 0.1e1 / h;
			double t2 = Math.pow(t1, 2);
			double t3 = Math.pow(t2, 2);
			double t4 = d * r;
			double t5 = t4 * t2;
			double t6 = Math.sqrt(t5);
			t5 = Math.pow(t5, 0.3e1 / 0.2e1);
			double t7 = Math.pow(t5, -0.2e1 / 0.3e1);
			double t8 = 0.1e1 / r;
			double t9 = h * t8;
			double t10 = Math.sqrt(t9);
			double t11 = Math.pow(t8, 2);
			double t12 = h * h * t11;
			double t13 = A2 + t12;
			double t14 = A1 * t10 * t13 + A0;
			t12 = t12 * B1 + B0;
			double t15 = d * d;
			double t16 = r * r;
			double t17 = d * t15 * r * t16 * t12 * t2 * t3 + P3 * t5 * t7 + t14 * t5 + P2;
			t9 = t13 * Math.pow(t9, -0.1e1 / 0.2e1) + 4 * t9 * t10;
			t7 = P3 * t7;
			t10 = t12 * t16;
			t12 = t10 * t2;
			t13 = (-t7 - 3 * t14) * t6;
			t16 = t2 * t15;
			t1 = t4 * t1 * t2;
			t4 = 0.1e1 / t17;
			t17 = t4 * (P0 + P1 * t5);
			t1 = -t4
					* (t17 * (t5 * A1 * t8 * t9 / 2 + t1 * (2 * t16 * (B1 - 3 * t12) + t13)) + 3
							* t1 * P1 * t6);
			t8 = d * t2;
			t5 = t4
					* (3 * t8 * P1 * t6 - 2
							* t17
							* (-t5 * A1 * h * t11 * t9 / 2 + t8
									* (-t13 / 2 + t16 * (-2 * B1 + 3 * t12)))) / 2;
			t2 = r * t2;
			t2 = t4
					* (3 * t2 * P1 * t6 - 2 * t17 * t2
							* (t6 * (0.3e1 / 0.2e1 * t14 + t7 / 2) + 3 * t3 * t10 * t15)) / 2;
			t1 = Math.pow(t2, 2) * sd * sd + Math.pow(t1, 2) * sh * sh + Math.pow(t5, 2) * sr
					* sr;

			double sysErr = LONG_PARAM_PSI_RELATIVE_ERROR * longSlipPsi.get(i);

			double error = Math.sqrt(t1 + sysErr * sysErr);

			longSlipPsiError.add(error);
		}

	}

	/**
	 * Evaluates the errors for psi of the long model in the slipping case. Adds
	 * an additional systematic error due to fitting discrepancy (see paper)
	 * 
	 * @see Data#LONG_PARAM_PSI_RELATIVE_ERROR
	 */
	private void evaluateErrorsLongNoSlipPsi() {
		longNoSlipPsiError = new ArrayList<Double>();
		for (int i = 0; i < indentations.size(); i++) {
			double r = radius;
			double sr = radiusError;
			double h = gelHeights.get(i);
			double sh = gelHeightError;
			double d = indentations.get(i);
			double sd = indentationError;
			double P0 = LONG_PARAM_PSI_0;
			double P1 = LONG_PARAM_PSI_1;
			double P2 = LONG_PARAM_PSI_2;
			double P3 = LONG_PARAM_PSI_3;
			double A0 = LONG_PARAM_NO_SLIP_ALPHA_0;
			double A1 = LONG_PARAM_NO_SLIP_ALPHA_1;
			double B0 = LONG_PARAM_NO_SLIP_BETA_0;

			double t1 = 0.1e1 / h;
			double t2 = Math.pow(t1, 2);
			double t3 = Math.pow(t2, 2);
			double t4 = d * r;
			double t5 = t4 * t2;
			double t6 = Math.sqrt(t5);
			t5 = Math.pow(t5, 0.3e1 / 0.2e1);
			double t7 = Math.pow(t5, -0.2e1 / 0.3e1);
			double t8 = 0.1e1 / r;
			double t9 = Math.pow(t8, 2);
			double t10 = A1 * h * h;
			double t11 = t10 * t9 + A0;
			double t12 = d * d;
			double t13 = r * r;
			double t14 = B0 * d * t12 * r * t13 * t2 * t3 + P3 * t5 * t7 + t11 * t5 + P2;
			t7 = P3 * t7;
			double t15 = (3 * t11 + t7) * t6;
			t3 = B0 * t12 * t3 * t13;
			t1 = t4 * t1 * t2;
			t4 = 0.1e1 / t14;
			t12 = t4 * (P0 + P1 * t5);
			t1 = -t4
					* (t12 * (2 * A1 * h * t5 * t9 + t1 * (-6 * t3 - t15)) + 3 * t1 * P1 * t6);
			t13 = d * t2;
			t5 = t4
					* (3 * t13 * P1 * t6 - 2 * t12
							* (-2 * t10 * t8 * t9 * t5 + t13 * (3 * t3 + t15 / 2))) / 2;
			t2 = r * t2;
			t2 = t4
					* (3 * t2 * P1 * t6 - 2 * t12 * t2
							* (t6 * (0.3e1 / 0.2e1 * t11 + t7 / 2) + 3 * t3)) / 2;
			t1 = Math.pow(t2, 2) * sd * sd + sh * sh * Math.pow(t1, 2) + Math.pow(t5, 2) * sr
					* sr;

			double sysErr = LONG_PARAM_PSI_RELATIVE_ERROR * longNoSlipPsi.get(i);

			double error = Math.sqrt(t1 + sysErr * sysErr);

			longNoSlipPsiError.add(error);
		}
	}

	/**
	 * Evaluates the errors for the elastic modulus of the long model in the
	 * slipping case
	 */
	private void evaluateErrorsLongSlipElasticModulus() {
		elasticModulusLongSlipError = new ArrayList<Double>();
		for (int i = 0; i < indentations.size(); i++) {
			double t1 = elasticModulusHertz.get(i) * longSlipPsiError.get(i);
			double t2 = longSlipPsi.get(i) * elasticModulusHertzError.get(i);
			double error = Math.sqrt(t1 * t1 + t2 * t2);
			elasticModulusLongSlipError.add(error);
		}
	}

	/**
	 * evaluates the error of the dimitriadis elastic modulus using the errors
	 * of the Hertz elastic modulus and the dimitriadis factors. The calculation
	 * is performed using standard error propagation.
	 */
	private void evaluateErrorsElasticModulusDimitriadis() {
		elasticModulusDimitriadisError = new ArrayList<Double>();
		for (int i = 0; i < dimitriadisFactors.size(); i++) {

			double diff_EDim_by_EHertz = 1 / dimitriadisFactors.get(i);
			double diff_EDim_by_fact = -elasticModulusHertz.get(i)
					/ (dimitriadisFactors.get(i) * dimitriadisFactors.get(i));

			// error propagation
			diff_EDim_by_EHertz *= diff_EDim_by_EHertz * elasticModulusHertzError.get(i)
					* elasticModulusHertzError.get(i);
			diff_EDim_by_fact *= diff_EDim_by_fact * dimitriadisFactorErrors.get(i)
					* dimitriadisFactorErrors.get(i);

			double error = Math.sqrt(diff_EDim_by_fact + diff_EDim_by_EHertz);

			elasticModulusDimitriadisError.add(error);
		}
	}

	/**
	 * Evaluates the error of the Dimitriadis parameter alpha0 by running error
	 * propagation on the error of nu.
	 */
	private void evaluateErrorAlpha0Dimitriadis() {
		double diff_alpha0_by_nu = ((DIMITRIADIS_PARAM_ALPHA0_1 + 2
				* DIMITRIADIS_PARAM_ALPHA0_2 * poissonValue)
				* (1 - poissonValue) + (DIMITRIADIS_PARAM_ALPHA0_0
				+ DIMITRIADIS_PARAM_ALPHA0_1 * poissonValue + DIMITRIADIS_PARAM_ALPHA0_2
				* poissonValue * poissonValue))
				/ ((1 - poissonValue) * (1 - poissonValue));

		diff_alpha0_by_nu *= diff_alpha0_by_nu * poissonValueError * poissonValueError;

		dimAlpha0BondedError = Math.sqrt(diff_alpha0_by_nu);
	}

	/**
	 * Evaluates the error of the Dimitriadis parameter beta0 by running error
	 * propagation on the error of nu.
	 */
	private void evaluateErrorBeta0Dimitriadis() {
		double diff_beta0_by_nu = ((DIMITRIADIS_PARAM_BETA0_1 + DIMITRIADIS_PARAM_BETA0_2 * 2
				* poissonValue)
				* (1 - poissonValue) + (DIMITRIADIS_PARAM_BETA0_0 + DIMITRIADIS_PARAM_BETA0_1
				* poissonValue + DIMITRIADIS_PARAM_BETA0_2 * poissonValue * poissonValue))
				/ ((1 - poissonValue) * (1 - poissonValue));
		diff_beta0_by_nu *= diff_beta0_by_nu * poissonValueError * poissonValueError;
		dimBeta0BondedError = Math.sqrt(diff_beta0_by_nu);
	}

	/**
	 * Evaluates the error of the dimitriadis parameter chi for all given
	 * datapoints. The calulation takes the errors of radius, indentation and
	 * gel height into account.
	 */
	private void evaluateErrorChiDimitriadis() {
		dimChiError = new ArrayList<Double>();
		for (int i = 0; i < indentations.size(); i++) {
			double diff_chi_by_radius = 0.5 * Math.sqrt(indentations.get(i) / radius)
					/ gelHeights.get(i);
			double diff_chi_by_indent = 0.5 * Math.sqrt(radius / indentations.get(i))
					/ gelHeights.get(i);
			double diff_chi_by_gel_height = Math.sqrt(radius * indentations.get(i))
					/ (gelHeights.get(i) * gelHeights.get(i));

			// error propagation
			diff_chi_by_radius *= diff_chi_by_radius * radiusError * radiusError;
			diff_chi_by_indent *= diff_chi_by_indent * indentationError * indentationError;
			diff_chi_by_gel_height *= diff_chi_by_gel_height * gelHeightError * gelHeightError;

			dimChiError.add(Math.sqrt(diff_chi_by_gel_height + diff_chi_by_indent
					+ diff_chi_by_radius));
		}
	}

	/**
	 * Evaluates the errors of the dimitriadis factors by combining the errors
	 * of alpha0, beta0 and chi using standard error analysis.
	 */
	private void evaluateErrorDimitriadisFactors() {
		dimitriadisFactorErrors = new ArrayList<Double>();

		for (int i = 0; i < indentations.size(); i++) {
			double chi = this.dimChi.get(i);
			double chi_div_pi = chi / Math.PI;

			double diff_fact_by_alpha0_order1 = -2 * chi / Math.PI;
			double diff_fact_by_alpha0_order2 = 8 * dimAlpha0Bonded
					* (chi_div_pi * chi_div_pi);
			double diff_fact_by_alpha0_order3 = -24 * dimAlpha0Bonded * dimAlpha0Bonded
					* chi_div_pi * chi_div_pi * chi_div_pi;
			double diff_fact_by_alpha0_order4 = 16 * 4 * dimAlpha0Bonded * dimAlpha0Bonded
					* dimAlpha0Bonded * chi_div_pi * chi_div_pi * chi_div_pi * chi_div_pi;
			double diff_fact_by_alpha0 = diff_fact_by_alpha0_order1
					+ diff_fact_by_alpha0_order2 + diff_fact_by_alpha0_order3
					+ diff_fact_by_alpha0_order4;

			double diff_fact_by_beta0_order3 = -32 * Math.PI * Math.PI * chi_div_pi
					* chi_div_pi * chi_div_pi / 15;
			double diff_fact_by_beta0_order4 = 16 * 3 * dimAlpha0Bonded * Math.PI * Math.PI
					* chi_div_pi * chi_div_pi * chi_div_pi * chi_div_pi / 5;
			double diff_fact_by_beta0 = diff_fact_by_beta0_order3 + diff_fact_by_beta0_order4;

			double diff_fact_by_chi_order1 = -2 * dimAlpha0Bonded / Math.PI;
			double diff_fact_by_chi_order2 = 8 * dimAlpha0Bonded * dimAlpha0Bonded
					* chi_div_pi * chi;
			double diff_fact_by_chi_order3 = -8
					* 3
					* (dimAlpha0Bonded * dimAlpha0Bonded * dimAlpha0Bonded + 4 * Math.PI
							* Math.PI * dimBeta0Bonded / 15.) * chi_div_pi * chi_div_pi
					/ Math.PI;
			double diff_fact_by_chi_order4 = 16
					* 4
					* dimAlpha0Bonded
					* (dimAlpha0Bonded * dimAlpha0Bonded * dimAlpha0Bonded + 3 * Math.PI
							* Math.PI * dimBeta0Bonded / 5) * chi_div_pi * chi_div_pi
					* chi_div_pi / Math.PI;

			double diff_fact_by_chi = diff_fact_by_chi_order1 + diff_fact_by_chi_order2
					+ diff_fact_by_chi_order3 + diff_fact_by_chi_order4;

			// error propagation
			diff_fact_by_alpha0 *= diff_fact_by_alpha0 * dimAlpha0BondedError
					* dimAlpha0BondedError;
			diff_fact_by_beta0 *= diff_fact_by_beta0 * dimBeta0BondedError
					* dimBeta0BondedError;
			diff_fact_by_chi *= diff_fact_by_chi * dimChiError.get(i) * dimChiError.get(i);

			double error = Math.sqrt(diff_fact_by_alpha0 + diff_fact_by_beta0
					+ diff_fact_by_chi);
			dimitriadisFactorErrors.add(error);
		}

	}

	/**
	 * Calculates the raw graviational force of a body.
	 * 
	 * @param volume
	 *            the volume of the body in m^3.
	 * @param density
	 *            the density of the body in kg/m^3.
	 * @return the graviational force in m^2/s.
	 */
	private double gravitationalForce(double volume, double density) {
		return GRAVITATIONAL_ACCELERATION * volume * density;
	}

	/**
	 * Calculates the volume of a sphere.
	 * 
	 * @param radius
	 *            the radius of the sphere in m
	 * @return the volume of the sphere in m^3
	 */
	private double sphereVolume(double radius) {
		return 4 * Math.PI * radius * radius * radius / 3;
	}

	/**
	 * Calculates the volume of a spherical cap. If the capHeight > radius *
	 * 2.0000001, a RuntimeException is thrown.
	 * 
	 * @param radius
	 *            the radius of curvatur of the spherical cap in m
	 * @param capHeight
	 *            capHeight in m
	 * @return Cap Volume in m^3
	 */
	public static double sphericalCapVolume(double radius, double capHeight) {
		if (capHeight > radius * 2.0000001) {
			throw new RuntimeException("radius<capHeight detected");
		}
		return capHeight * capHeight * Math.PI * (3 * radius - capHeight) / 3;
	}

	/**
	 * Calculates the surface area of a spherical cap. If the capHeight > radius
	 * * 2.0000001, a RuntimeException is thrown.
	 * 
	 * @param radius
	 *            the radius of curvatur of the spherical cap in m
	 * @param capHeight
	 *            capHeight in m
	 * @return surface area of the spherical cap in m^2
	 */
	public static double sphericalCapSurfaceArea(double radius, double capHeight) {
		if (capHeight > radius * 2.0000001) {
			throw new RuntimeException("radius<capHeight detected");
		}
		return 2 * Math.PI * radius * capHeight;
	}

	/**
	 * Calculates the error of a mean value of a given value set. If statistical
	 * analysis is possible (N>5), the error is calculated by
	 * sqrt(sum(sigma_i^2)/(N-1)). Otherwise, standard error propagation is
	 * used.
	 * 
	 * @param values
	 *            the values of the dataset
	 * @param errors
	 *            the single errors of the values
	 * @return error of the mean value
	 */
	public static double calculateMeanValueError(List<Double> values, List<Double> errors) {
		double errorSquareSum = 0.;
		// if N<6, standard error propagation...
		if (errors.size() < 6) {
			for (int i = 0; i < errors.size(); i++) {
				errorSquareSum += errors.get(i) * errors.get(i);
			}
			return Math.sqrt(errorSquareSum / errors.size());
		}
		// if statistics are possible, use them
		else {
			errorSquareSum = 0;
			double mean = Statistics.mean(values);
			for (int i = 0; i < values.size(); i++) {
				double val = values.get(i);
				errorSquareSum += (mean - val) * (mean - val);
			}
			return Math.sqrt(errorSquareSum / (values.size() - 1));
		}
	}

	/**
	 * Data instances are ordered by name, temperature, the poisson value and
	 * finally the used sphere radius.
	 */
	public int compareTo(Data data) {
		// sort by sample name
		int ret = sampleName.compareTo(data.sampleName);
		if (ret != 0) {
			return ret;
		}
		// then temperature
		if (temperature > data.temperature) {
			return 1;
		} else if (temperature < data.temperature) {
			return -1;
		}
		// poisson value
		if (poissonValue > data.poissonValue) {
			return 1;
		} else if (poissonValue < data.poissonValue) {
			return -1;
		}
		// then radius
		if (radius > data.radius) {
			return 1;
		} else if (radius < data.radius) {
			return -1;
		}
		return 0;
	}

	/**
	 * @return {@link #sphereVolume}
	 */
	public double getSphereVolume() {
		return sphereVolume;
	}

	/**
	 * @return {@link #sphereCapInWaterVolume}
	 */
	public double getSphereCapInWaterVolume() {
		return sphereCapInWaterVolume;
	}

	/**
	 * @return {@link #buoyancyForce}
	 */
	public double getBuoyancyForce() {
		return buoyancyForce;
	}

	/**
	 * @return {@link #gravitationalForce}
	 */
	public double getGravitationalForce() {
		return gravitationalForce;
	}

	/**
	 * @return {@link #indentationForce}
	 */
	public double getIndentationForce() {
		return indentationForce;
	}

	/**
	 * @return {@link #indentationForceError}
	 */
	public double getIndentationForceError() {
		return indentationForceError;
	}

	/**
	 * @return {@link #minHeight}
	 */
	public double getMinHeight() {
		return minHeight;
	}

	/**
	 * @return {@link #maxHeight}
	 */
	public double getMaxHeight() {
		return maxHeight;
	}

	/**
	 * @return {@link #meanElasticModulusHertz}
	 */
	public double getMeanElasticModulusHertz() {
		return meanElasticModulusHertz;
	}

	/**
	 * @return {@link #meanElasticModulusHertzError}
	 */
	public double getMeanElasticModulusHertzError() {
		return meanElasticModulusHertzError;
	}

	/**
	 * @return {@link #meanElasticModulusDimitriadis}
	 */
	public double getMeanElasticModulusDimitriadis() {
		return meanElasticModulusDimitriadis;
	}

	/**
	 * @return {@link #meanElasticModulusDimitriadisError}
	 */
	public double getMeanElasticModulusDimitriadisError() {
		return meanElasticModulusDimitriadisError;
	}

	/**
	 * @return {@link #meanShearModulus}
	 */
	public double getMeanShearModulus() {
		return meanShearModulus;
	}

	/**
	 * @return {@link #meanShearModulusError}
	 */
	public double getMeanShearModulusError() {
		return meanShearModulusError;
	}

	/**
	 * @return {@link #meanBulkModulus}
	 */
	public double getMeanBulkModulus() {
		return meanBulkModulus;
	}

	/**
	 * @return {@link #meanBulkModulusError}
	 */
	public double getMeanBulkModulusError() {
		return meanBulkModulusError;
	}

	/**
	 * @return {@link #elasticModulusHertz}
	 */
	public ArrayList<Double> getElasticModulusHertz() {
		return elasticModulusHertz;
	}

	/**
	 * @return {@link #elasticModulusHertzError}
	 */
	public ArrayList<Double> getElasticModulusHertzError() {
		return elasticModulusHertzError;
	}

	/**
	 * @return {link #elasticModulusLongSlip}
	 */
	public ArrayList<Double> getElasticModulusLongSlip() {
		return elasticModulusLongSlip;
	}

	/**
	 * @return {link #elasticModulusLongNoSlip}
	 */
	public ArrayList<Double> getElasticModulusLongNoSlip() {
		return elasticModulusLongNoSlip;
	}

	/**
	 * @return {@link #dimitriadisFactors}
	 */
	public ArrayList<Double> getDimitriadisFactors() {
		return dimitriadisFactors;
	}

	/**
	 * @return {@link #dimitriadisFactorErrors}
	 */
	public ArrayList<Double> getDimitriadisFactorErrors() {
		return dimitriadisFactorErrors;
	}

	/**
	 * @return {@link #elasticModulusDimitriadis}
	 */
	public ArrayList<Double> getElasticModulusDimitriadis() {
		return elasticModulusDimitriadis;
	}

	/**
	 * @return {@link #elasticModulusDimitriadisError}
	 */
	public ArrayList<Double> getElasticModulusDimitriadisError() {
		return elasticModulusDimitriadisError;
	}

	/**
	 * @return {@link #shearModulus}
	 */
	public ArrayList<Double> getShearModulus() {
		return shearModulus;
	}

	/**
	 * @return {@link #shearModulusError}
	 */
	public ArrayList<Double> getShearModulusError() {
		return shearModulusError;
	}

	/**
	 * @return {@link #bulkModulus}
	 */
	public ArrayList<Double> getBulkModulus() {
		return bulkModulus;
	}

	/**
	 * @return {@link #bulkModulusError}
	 */
	public ArrayList<Double> getBulkModulusError() {
		return bulkModulusError;
	}

	/**
	 * @return {@link #indentationPercent}
	 */
	public ArrayList<Double> getIndentationPercent() {
		return indentationPercent;
	}

	/**
	 * @return {@link #indentationVolume}
	 */
	public ArrayList<Double> getIndentationVolume() {
		return indentationVolume;
	}

	/**
	 * @return {@link #contactArea}
	 */
	public ArrayList<Double> getContactArea() {
		return contactArea;
	}

	/**
	 * @return {@link #dimChiLessThanOne}
	 */
	public ArrayList<Boolean> getChiLessThanOne() {
		return dimChiLessThanOne;
	}

	/**
	 * @return {@link #indentLessThenLinHeightPercentage}
	 */
	public ArrayList<Boolean> getIndentLessThenLinHeightPercentage() {
		return indentLessThenLinHeightPercentage;
	}

	/**
	 * @return {@link #dimChi}
	 */
	public ArrayList<Double> getDimChi() {
		return dimChi;
	}

	public ArrayList<Double> getForcePerContaceArea() {
		return forcePerContaceArea;
	}

	public ArrayList<Double> getLongSlipPsi() {
		return longSlipPsi;
	}

	public ArrayList<Double> getLongNoSlipPsi() {
		return longNoSlipPsi;
	}

	public double getMeanElasticModulusLongNoSlip() {
		return meanElasticModulusLongNoSlip;
	}

	public double getMeanElasticModulusLongNoSlipError() {
		return meanElasticModulusLongNoSlipError;
	}

	public double getMeanElasticModulusLongSlip() {
		return meanElasticModulusLongSlip;
	}

	public double getMeanElasticModulusLongSlipError() {
		return meanElasticModulusLongSlipError;
	}

	public ArrayList<Double> getElasticModulusLongSlipError() {
		return elasticModulusLongSlipError;
	}

	public ArrayList<Double> getElasticModulusLongNoSlipError() {
		return elasticModulusLongNoSlipError;
	}

	public ArrayList<Boolean> getLongDeltaDivHValid() {
		return longDeltaDivHValid;
	}

	public ArrayList<Boolean> getLongRDivHValid() {
		return longRDivHValid;
	}
}

interface DataPrinting {
	public void printData(Data data, String outputFileName, File outputFileDirectory);
}

class ExtendedDataPrinter implements DataPrinting {

	@Override
	public void printData(Data data, String outputFileName, File outputFileDirectory) {
		try {
			BufferedWriter bw = new BufferedWriter(new FileWriter(new File(outputFileDirectory,
					outputFileName)));

			Date timestamp = new Date();
			SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");

			// sample name
			printLine(
					"RESULT SHEET for " + data.sampleName + " (processed "
							+ simpleDateFormat.format(timestamp) + ")", bw);

			// temperature
			// printLine(data.temperature + "\ttemperature (C)", bw);

			// radius
			printLine(data.radius / 1E-6 + "\tsphere radius(um)", bw);

			// radius error
			// printLine(data.radiusError / 1E-6 + "\tsphere radius error(um)",
			// bw);

			// poisson value
			printLine(data.poissonValue + "\tpoisson ratio", bw);

			// poisson value error
			// printLine(data.poissonValueError + "\tpoisson ratio error", bw);

			// sphere density
			// printLine(data.sphereDensity + "\tsphere density kg/m^3", bw);

			// sphere density error
			// printLine(data.sphereDensityError +
			// "\tsphere density error kg/m^3", bw);

			// water density
			// printLine(data.waterDensity + "\twater density kg/m^3", bw);

			// water density error
			// printLine(data.waterDensityError +
			// "\twater density error kg/m^3", bw);

			// cap height in water
			// printLine(data.capHeightInWater / 1E-6 +
			// "\tcap height in water um", bw);

			// cap height in water error
			// printLine(data.capHeightInWaterError / 1E-6 +
			// "\tcap height in water error um", bw);

			// indentation error
			// printLine(data.indentationError / 1E-6 +
			// "\tindentation error um", bw);

			// gel height error
			// printLine(data.gelHeightError / 1E-6 + "\tgel height error um",
			// bw);

			// indentation Force
			printLine(data.getIndentationForce() + "\tindentationForce N", bw);

			// gel height
			printLine(data.getMinHeight() / 1E-6 + " - " + data.getMaxHeight() / 1E-6
					+ "\t gel height range", bw);

			// mean values
			printLine(
					data.getMeanElasticModulusHertz() + " +- "
							+ data.getMeanElasticModulusHertzError() + "\t <E_hertz> kPa", bw);
			printLine(
					data.getMeanElasticModulusDimitriadis() + " +- "
							+ data.getMeanElasticModulusDimitriadisError() + "\t <E_dim> kPa", bw);

			ASCIITable asciiTable = createAsciiTable(data);
			bw.write(asciiTable.getTable().replaceAll(" +", "\t"));

			bw.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private ASCIITable createAsciiTable(Data data) {
		int doubleColWidth = 12;
		ASCIITable table = new ASCIITable();
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("descr", "-"));
			table.addColumn(new ASCIIStringColumn(headlines, data.lineNames, 8));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("height", "um"));
			table.addColumn(new ASCIIDoubleColumn(headlines, data.gelHeights, -6, 1, doubleColWidth));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("s_height", "um"));
			ArrayList<Double> heightErr = new ArrayList<Double>();
			for (int i = 0; i < data.gelHeights.size(); i++) {
				heightErr.add(data.gelHeightError);
			}
			table.addColumn(new ASCIIDoubleColumn(headlines, heightErr, -6, 1, 10));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("indent", "um"));
			table.addColumn(new ASCIIDoubleColumn(headlines, data.indentations, -6, 3, 8));
		}

		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("s_indent", "um"));
			ArrayList<Double> indErr = new ArrayList<Double>();
			for (int i = 0; i < data.indentations.size(); i++) {
				indErr.add(data.indentationError);
			}
			table.addColumn(new ASCIIDoubleColumn(headlines, indErr, -6, 1, doubleColWidth));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("indent/height", "%"));
			table.addColumn(new ASCIIDoubleColumn(headlines, data.getIndentationPercent(), -2, 1,
					15));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("E_Hertz", "kPa"));
			table.addColumn(new ASCIIDoubleColumn(headlines, data.getElasticModulusHertz(), 3, 2,
					doubleColWidth));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("s_E_Hertz", "kPa"));
			table.addColumn(new ASCIIDoubleColumn(headlines, data.getElasticModulusHertzError(), 3,
					2, doubleColWidth));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("E_Dimitr", "kPa"));
			table.addColumn(new ASCIIDoubleColumn(headlines, data.getElasticModulusDimitriadis(),
					3, 2, doubleColWidth));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("s_E_Dimitr", "kPa"));
			table.addColumn(new ASCIIDoubleColumn(headlines, data
					.getElasticModulusDimitriadisError(), 3, 2, doubleColWidth));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("Dimitr.Fact", "-"));
			table.addColumn(new ASCIIDoubleColumn(headlines, data.getDimitriadisFactors(), 0, 2,
					doubleColWidth));
		}
		// {
		// ArrayList<String> headlines = new
		// ArrayList<>(Arrays.asList("s_Dimitr.Fact", ""));
		// table.addColumn(new ASCIIDoubleColumn(headlines,
		// data.getDimitriadisFactorErrors(), 0, 0, 15));
		// }
		// {
		// ArrayList<String> headlines = new
		// ArrayList<>(Arrays.asList("ContactArea", "um^2"));
		// table.addColumn(new ASCIIDoubleColumn(headlines,
		// data.getContactArea(), -12, 0, 12));
		// }
		// {
		// ArrayList<String> headlines = new
		// ArrayList<>(Arrays.asList("chi kl 1", "-"));
		// table.addColumn(new ASCIIBooleanColumn(headlines,
		// data.getChiLessThanOne(), 9));
		// }
		// {
		// ArrayList<String> headlines = new ArrayList<>(Arrays.asList("d kl" +
		// Data.MAX_INDENT_PERCENTAGE_FOR_LINEARITY
		// + "h", "-"));
		// table.addColumn(new ASCIIBooleanColumn(headlines,
		// data.getIndentLessThenLinHeightPercentage(), 9));
		// }
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("E_Long_S", "kPa"));
			table.addColumn(new ASCIIDoubleColumn(headlines, data.getElasticModulusLongSlip(), 3,
					2, doubleColWidth + 3));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("s_E_Long_S", "kPa"));
			table.addColumn(new ASCIIDoubleColumn(headlines, data.getElasticModulusLongSlipError(),
					3, 2, doubleColWidth + 3));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("E_Long_NS", "kPa"));
			table.addColumn(new ASCIIDoubleColumn(headlines, data.getElasticModulusLongNoSlip(), 3,
					2, doubleColWidth + 3));
		}
		{
			ArrayList<String> headlines = new ArrayList<>(Arrays.asList("s_E_Long_NS", "kPa"));
			table.addColumn(new ASCIIDoubleColumn(headlines, data
					.getElasticModulusLongNoSlipError(), 3, 2, doubleColWidth + 3));
		}
		// Funzt nicht mit origin import
		// {
		// ArrayList<String> headlines = new
		// ArrayList<>(Arrays.asList(Data.LONG_R_DIV_H_VALIDITY_LOWER_BOUND
		// + "<R div h<" + Data.LONG_R_DIV_H_VALIDITY_UPPER_BOUND, "-"));
		// table.addColumn(new ASCIIBooleanColumn(headlines,
		// data.getLongRDivHValid(), 18));
		// }
		// {
		// ArrayList<String> headlines = new
		// ArrayList<>(Arrays.asList("d/h<Data.LONG_DELTA_DIV_H_VALIDITY_UPPER_BOUND",
		// "-"));
		// table.addColumn(new ASCIIBooleanColumn(headlines,
		// data.getLongDeltaDivHValid(), 50));
		// }
		// {
		// ArrayList<String> headlines = new
		// ArrayList<>(Arrays.asList("avg Pressure", "Pa"));
		// table.addColumn(new ASCIIDoubleColumn(headlines,
		// data.getForcePerContaceArea(), 0, 2,
		// doubleColWidth + 3));
		// }
		return table;
	}

	private void printLine(String line, BufferedWriter bw) throws IOException {
		bw.write(line);
		bw.newLine();
	}

}
